#include <QHostAddress>
#include <QTimer>
#include "waitforack.h"
#include "telnetsocket.h"
#include "macro.h"

TelnetSocket::TelnetSocket(quint16 port,QByteArray nodeIP,qint64 subAddr,QObject *parent) :
    QTcpSocket(parent)
{
    countTTL=0;
    telnetPort=port;
    telnetIP=nodeIP;
    waitForACK=new WaitForAck(this);
    connect(waitForACK,SIGNAL(sigPacketToNodeAgain(QByteArray,qint64)),this,SLOT(slotPacketToNodes(QByteArray,qint64)));
    connect(waitForACK,SIGNAL(sigErrorInfo(QByteArray)),this,SIGNAL(sigShowInfoToErrorTextEdit(QByteArray)));

    timerSendTcpData=new QTimer(this);
    timerSendTcpData->start(TelnetSendInterval);
    connect(timerSendTcpData,SIGNAL(timeout()),this,SLOT(slotTimerEventSendTcpData()));

    timerSendHeartPacket=new QTimer(this);
    timerSendHeartPacket->start(HeartToIOTServerInterval);
    connect(timerSendHeartPacket,SIGNAL(timeout()),this,SLOT(slotTimerEventSendHeartPacket()));

    listNodesSubAddr.append(subAddr);
    timerIntervalSeconds=TelnetReconnectIntervalMinimum; //定时器间隔
    timerIntervalFactor=TelnetReconnectIntervalFactor;   //每次定时器间隔乘以这么多
    reconnectTimes=0; //断开后尝试重连的次数
    connectToHost(nodeIP,port);

    connect(this,SIGNAL(readyRead()),this,SLOT(slotProcessTcpDatagrams()));
    connect(this,SIGNAL(connected()),this,SLOT(slotTcpConnected()));
    connect(this,SIGNAL(disconnected()),this,SLOT(slotTcpDisconnected()));
    connect(this,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(slotTcpError()));
}

void TelnetSocket::slotProcessTcpDatagrams()
{
    if(!tcpData.toHex().startsWith(HEADER)) //数据包应当是以“aa44"开头
    {
        tcpData.clear();
    }
    tcpData.append(readAll());
    if(!tcpData.toHex().startsWith(HEADER)) //数据包应当是以“aa44"开头
    {
        tcpData.clear();
        return;
    }

    bool ok;

    while(tcpData.size()>(Len_Pos+Len_Size+1))
    {
        qint32 len=tcpData.mid(Len_Pos,Len_Size).toHex().toInt(&ok,16); //aa44协议中，长度在第20到21字节
        if(tcpData.size()>=len+4&&tcpData.size()>=Dest_Pos+Dest_Size-1)
        {            
            qint64 nodeAddr=tcpData.mid(Source_Pos,Source_Size).toHex().toLongLong(&ok,16);
            if(!listNodesAddrForHeart.contains(nodeAddr)&&nodeAddr!=0)
                listNodesAddrForHeart.append(nodeAddr);
            qint64 mask= Q_INT64_C(0x00ffffffffffffff);
            qint64 subAddr=nodeAddr&mask;
            if(!listNodesSubAddr.contains(subAddr)&&subAddr!=0)
                listNodesSubAddr.append(subAddr);

            countTTL=TTL_MAX;
            quint8 sN=tcpData.mid(SN_Pos,SN_Size).toHex().toUShort(&ok,16);
            quint8 pktType=tcpData.mid(PktType_Pos,PktType_Size).toHex().toUShort(&ok,16);
            emit sigPacketReady(tcpData.mid(0,len+4)); //把接收到的节点的数据包发送给主线程

            if(pktType==PktConfiACK)
            {
                doWithConfiACK(tcpData,nodeAddr,sN);
            }

            tcpData.remove(0,len+4);
        }
        else
            break;
    }

    if(tcpData.size()>Max_Packet_Size)
    {
        emit sigPacketReady(tcpData); //数据包大小超过默认，则全部发送出去
        tcpData.clear();
    }
}

void TelnetSocket::slotTcpConnected()
{
    timerIntervalSeconds=TelnetReconnectIntervalMinimum; //定时器间隔
    reconnectTimes=0;
    emit sigShowInfoToTextEdit("connected");
    emit sigTelnetConnected(telnetIP,telnetPort);
}

void TelnetSocket::slotTcpDisconnected()
{
    emit sigTelnetDisconnectedTemporarily(telnetIP,telnetPort);
    emit sigShowInfoToTextEdit(telnetIP+" disconnected");

    QTimer::singleShot(timerIntervalSeconds*1000,this,SLOT(slotTimerSingleShotForReconnect()));
    timerIntervalSeconds=timerIntervalSeconds*timerIntervalFactor;
    reconnectTimes++;
}

void TelnetSocket::slotTcpError()
{  
    reconnectTimes++;
    if(reconnectTimes>TelnetReconnectTimes) //重连次数过多，销毁线程
    {
        emit sigShowInfoToTextEdit(errorString().toUtf8());
        emit sigExitThread(telnetIP,telnetPort);
    }
    else
    {
        QTimer::singleShot(timerIntervalSeconds*1000,this,SLOT(slotTimerSingleShotForReconnect()));
        emit sigShowInfoToTextEdit("ERROR in telnetsocket for "+telnetIP+":"+errorString().toUtf8());
        timerIntervalSeconds=timerIntervalSeconds*timerIntervalFactor;
    }
}

void TelnetSocket::slotTimerSingleShotForReconnect()
{
    emit sigShowInfoToTextEdit("Trying to reconnect "+telnetIP+". "+QByteArray::number(reconnectTimes));
    if(reconnectTimes>TelnetReconnectTimes) //重连次数过多，销毁线程
        emit sigExitThread(telnetIP,telnetPort);
    else
        connectToHost(telnetIP,telnetPort);

}

void TelnetSocket::slotPacketToNodes(QByteArray packet, qint64 destSubAddr)
{
    if(!listNodesSubAddr.contains(destSubAddr))
        return;
    else
    {
        if(listTcpPacket.contains(packet))
            return;  //如果还有未发的相同的包，则先不发送
        listTcpPacket.append(packet);
        if(listTcpPacket.size()>TelnetMaxBufferPackets)
            listTcpPacket.removeFirst();
    }
}

void TelnetSocket::slotTimerEventSendTcpData()
{
    if(listTcpPacket.size()<=0)
        return;

    bool ok;

    QByteArray tcpData=listTcpPacket.at(0);
    qint64 bytes=write(tcpData);
    quint8 pktType=tcpData.mid(PktType_Pos,PktType_Size).toHex().toUShort(&ok,16);

    if(pktType==PktConfiACK)
    {
        qint64 nodeAddr=tcpData.mid(Dest_Pos,Dest_Size).toHex().toLongLong(&ok,16);
        quint8 sN=tcpData.mid(SN_Pos,SN_Size).toHex().toUShort(&ok,16);
        waitForACK->addOnePacket(nodeAddr,sN,tcpData); //如果已经添加过，则该函数不会作任何操作
    }

    emit sigShowInfoToTextEdit(listTcpPacket.at(0).toHex()+" is sent"
              ": "+QByteArray::number(bytes)+" bytes sent");
    listTcpPacket.removeFirst();
}

void TelnetSocket::slotTimerEventSendHeartPacket()
{
    if(countTTL<=0)
    {
        listNodesAddrForHeart.clear();
        return;
    }
    else
        countTTL-=HeartToIOTServerInterval/1000;

    QByteArray packetHex;
    for(int n=listNodesAddrForHeart.size();n>=1;n--)
    {
        qint64 addr=listNodesAddrForHeart.at(n-1);
        packetHex="aa4401"; //发送给服务器，上行数据
        packetHex.append("5555555555555555"); //Dest
        packetHex.append(mySetNumHex(addr,16)); //src
        packetHex.append("01");//SN
        packetHex.append(mySetNumHex(24,4));//Len
        packetHex.append("71"); //packet type
        packetHex.append("01"); //reserved
        packetHex.append("0100"); //data
        QByteArray packet=QByteArray::fromHex(packetHex);
        packet.append(QByteArray::fromHex(getCRC16Hex(packet.mid(2,packet.size()-2))));
        emit sigPacketReady(packet);
    }
}

void TelnetSocket::doWithConfiACK(QByteArray data,qint64 nodeAddr,quint8 sN)
{
    bool ok;
    quint8 dataType=data.mid(DataType_Pos,DataType_Size).toHex().toUShort(&ok,16);
    if(dataType==1) //配置信息回复
    {
        quint8 dataContent=data.mid(Data_Pos,1).toHex().toUShort(&ok,16);
        if(dataContent==0) //ACK
        {
            waitForACK->removeOnePacket(nodeAddr,sN);
            emit sigShowInfoToTextEdit(data.toHex()+" is Acked by "+
                       QByteArray::number(nodeAddr)+" ,SN is:"+QByteArray::number(sN));
        }
        else if (dataContent==1) //NACK
        {
            emit sigShowInfoToErrorTextEdit("ERROR: Config packet not recongnized by"+
                            QByteArray::number(nodeAddr)+" ,SN is:"+QByteArray::number(sN));
            waitForACK->removeOnePacket(nodeAddr,sN);
        }

    }
}
